/*
 *      MyLexer.java
 *
 *      Copyright 2008 Pablo Nicolas Diaz Bilotto <pablonicolas.diaz@gmail.com>
 *
 *      This program is free software; you can redistribute it and/or modify
 *      it under the terms of the GNU General Public License as published by
 *      the Free Software Foundation; either version 2 of the License, or
 *      (at your option) any later version.
 *
 *      This program is distributed in the hope that it will be useful,
 *      but WITHOUT ANY WARRANTY; without even the implied warranty of
 *      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *      GNU General Public License for more details.
 *
 *      You should have received a copy of the GNU General Public License
 *      along with this program; if not, write to the Free Software
 *      Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 *      MA 02110-1301, USA.
 */
package lexer;

import java.io.Reader;

import lexer.token.LiteralToken;
import lexer.token.SymbolizedToken;
import lexer.token.TaggedToken;
import lexer.token.Token;
import lexer.token.TokenEnum;
import symbolStorageService.ISymTable;
import symbolStorageService.SymEntry;
import errorService.IErrorReporter;


/**
 * The Class MyLexer.
 */
public class MyLexer extends BaseLexer<TransitionValue, StateEnum> {

	/** The sym table. */
	private ISymTable symTable;

	/** The buffer. */
	private StringBuffer buffer;

	/** The internal_ token. */
	private Token internal_Token;

	/**
	 * Instantiates a new my lexer.
	 *
	 * @param ReaderX the reader x
	 * @param S the s
	 * @param R the r
	 */
	public MyLexer(Reader ReaderX, ISymTable S, IErrorReporter R) {
		super(ReaderX, R);
		this.setSymTable(S);
		buffer = new StringBuffer("");
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see lexer.BaseLexer#Action(int, java.lang.Object, java.lang.Object, int)
	 */
	@Override
	protected void Action(int symbol, TransitionValue tr_sym, StateEnum state,
			int lineNumber) {
		switch (state) {

		case INITIAL:
			// inicializing buffer ;)
			if (buffer.length() > 0) {
				this.buffer.delete(0, buffer.length());
			}
			this.setToken(null);

			switch (tr_sym) {
			case LETTER:
				buffer.append((char) symbol);
				break;
			case DIGIT:
				buffer.append((char) symbol);
				break;
			case DOUBLE_COM:
				break;
			case LITERAL:
				this.setToken(LiteralToken.getInstance(lineNumber, symbol));
				break;
			case SLASH:
				break;
			case PLUS:
				this.setToken(LiteralToken.getInstance(lineNumber, symbol));
				break;
			case MINUS:
				this.setToken(LiteralToken.getInstance(lineNumber, symbol));
				break;
			case EMPTY_VALUE:
				break;
			case ASTERISK:
				this.setToken(LiteralToken.getInstance(lineNumber, symbol));
				break;
			case GREATER:
				break;
			case LESS:
				break;
			case EQUAL:
				break;
			case EXCL:
				break;
			case EOF:
				this.setToken(LiteralToken.getInstance(lineNumber, symbol));
				this.CloseSource();
				break;
			case DOT:
				break;
			case D:
				buffer.append((char) symbol);
				break;
			default:
				generateErrorToken(lineNumber, "Unrecognized Symbol ",
						Character.toString(((char) symbol)));
			}
			break;

		case IDENTIFIER:
			switch (tr_sym) {
			case LETTER:
				buffer.append((char) symbol);
				break;
			case D:
				buffer.append((char) symbol);
				break;
			case DIGIT:
				buffer.append((char) symbol);
				break;
			default:
				addSymTable("_" + buffer.toString(), lineNumber,
						TokenEnum.IDENTIFIER, EntryType.NOTYPE, "?");
				if (buffer.length() > 10)
					this.getReporter().reportWarning(lineNumber,
							"Lexer : Very Large ID");
				break;
			}
			break;

		case FIRST_D_COM:
			switch (tr_sym) {
			case DOUBLE_COM:
				addSymTable(("_STR_" + Math.abs(buffer.toString().hashCode())),
						lineNumber, TokenEnum.STRING, EntryType.STRING, "\'"
								+ buffer.toString().replaceAll("\'", "")
								+ "$\'");
				break;
			case EOF:
				this.getReporter().reportError(lineNumber, "Lexer : Unexpected EOF");
				this.setToken(LiteralToken.getInstance(lineNumber, symbol));
				this.CloseSource();
			default:
				buffer.append((char) symbol);
				break;
			}
			break;

		case SLASH:
			switch (tr_sym) {
			case ASTERISK:
				break;
			case EOF:
				this.getReporter().reportError(lineNumber,
						"Lexer: Unterminated Comment");
				this.getReporter().reportError(lineNumber, "Lexer : Unexpected EOF");
				this.CloseSource();
			default:
				this.setToken(LiteralToken.getInstance(lineNumber, (int) '/'));
				break;
			}
			break;

		case GREATER:
			switch (tr_sym) {
			case EQUAL:
				this.setToken(TaggedToken.getInstance(lineNumber,
						TokenEnum.GREATER_EQUAL));
				break;
			default:
				this.setToken(TaggedToken.getInstance(lineNumber,
						TokenEnum.GREATER));
				break;
			}
			break;

		case LESS:
			switch (tr_sym) {
			case EQUAL:
				this.setToken(TaggedToken.getInstance(lineNumber,
						TokenEnum.LESS_EQUAL));
				break;
			default:
				this.setToken(TaggedToken.getInstance(lineNumber,
						TokenEnum.LESS));
				break;
			}
			break;

		case EQUAL:
			switch (tr_sym) {
			case EQUAL:
				this.setToken(TaggedToken.getInstance(lineNumber,
						TokenEnum.EQUAL));
				break;
			default:
				this.setToken(LiteralToken.getInstance(lineNumber, (int) '='));
				break;
			}
			break;
		case EXCL:
			switch (tr_sym) {
			case EQUAL:
				this.setToken(TaggedToken.getInstance(lineNumber,
						TokenEnum.NOT_EQUAL));
				break;
			default:
				generateErrorToken(lineNumber, "Unrecognized Symbol", "" + '!');
				break;
			}
			break;

		case FIRST_ASTERISK:
			if (tr_sym == TransitionValue.EOF) {
				this.getReporter().reportError(lineNumber, "Lexer : Unexpected EOF");
				this.CloseSource();
			}
			break;

		case SECOND_ASTERISK:
			if (tr_sym == TransitionValue.EOF) {
				this.getReporter().reportError(lineNumber, "Lexer : Unexpected EOF");
				this.CloseSource();
			}
			break;

		case DIGIT:
			switch (tr_sym) {
			case DIGIT:
				buffer.append((char) symbol);
				break;
			case DOT:
				buffer.append((char) symbol);
				break;
			case D:
				buffer.append((char) 'e');
				break;
			default:
				parseInt(lineNumber, buffer.toString());
				break;
			}
			break;

		case DECIMAL_PART:
			switch (tr_sym) {
			case DIGIT:
				buffer.append((char) symbol);
				break;
			case D:
				buffer.append((char) 'e');
				break;
			default:
				parseDoble(lineNumber, buffer.toString());
				break;
			}
			break;

		case EXPONENT:
			switch (tr_sym) {
			case DIGIT:
				buffer.append((char) symbol);
				break;
			case PLUS:
				buffer.append((char) symbol);
				break;
			case MINUS:
				buffer.append((char) symbol);
				break;
			default:
				parseDoble(lineNumber, buffer.toString());
				break;
			}
			break;

		case EXP_VALUE:
			switch (tr_sym) {
			case DIGIT:
				buffer.append((char) symbol);
			default:
				parseDoble(lineNumber, buffer.toString());
				break;
			}
			break;

		case END:
			break;
		}
		;
	}

	/**
	 * Adds the sym table.
	 *
	 * @param thing the thing
	 * @param lineNumber the line number
	 * @param mKind the m kind
	 * @param mType the m type
	 * @param IniValue the ini value
	 */
	private void addSymTable(String thing, int lineNumber, TokenEnum mKind,
			EntryType mType, Object IniValue) {
		if (!this.getSymTable().containsKey(thing))
			this.getSymTable().put(thing,
					new SymEntry(lineNumber, mKind, mType, IniValue));
		this
				.setToken(new SymbolizedToken(thing, this.getSymTable().get(
						thing)));
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see lexer.BaseLexer#advanceStream(java.lang.Object, java.lang.Object)
	 */
	@Override
	protected boolean advanceStream(TransitionValue tr_sym, StateEnum state) {
		if (state != StateEnum.INITIAL && tr_sym == TransitionValue.EOF) {
			return false;
		}

		switch (state) {
		case INITIAL:
			if (tr_sym == TransitionValue.EOF) {
				return false;
			}
			return true;

		case IDENTIFIER:
			switch (tr_sym) {
			case LETTER:
				return true;
			case DIGIT:
				return true;
			case D:
				return true;
			default:
				return false;
			}

		case FIRST_D_COM:
			if (tr_sym == TransitionValue.EOF)
				return false;
			return true;

		case SLASH:
			switch (tr_sym) {
			case ASTERISK:
				return true;
			default:
				return false;
			}

		case DIGIT:
			switch (tr_sym) {
			case DIGIT:
				return true;
			case DOT:
				return true;
			case D:
				return true;
			default:
				return false;
			}

		case FIRST_ASTERISK:
			if (tr_sym == TransitionValue.EOF)
				return false;
			return true;

		case SECOND_ASTERISK:
			if (tr_sym == TransitionValue.EOF)
				return false;
			return true;

		case DECIMAL_PART:
			switch (tr_sym) {
			case DIGIT:
				return true;
			case D:
				return true;
			default:
				return false;
			}
		case EXPONENT:
			switch (tr_sym) {
			case DIGIT:
				return true;
			case PLUS:
				return true;
			case MINUS:
				return true;
			default:
				return false;
			}
		case EXP_VALUE:
			switch (tr_sym) {
			case DIGIT:
				return true;
			default:
				return false;
			}

		case END:
			return true;

		case GREATER:
			switch (tr_sym) {
			case EQUAL:
				return true;
			default:
				return false;
			}
		case LESS:
			switch (tr_sym) {
			case EQUAL:
				return true;
			default:
				return false;
			}
		case EQUAL:
			switch (tr_sym) {
			case EQUAL:
				return true;
			default:
				return false;
			}
		case EXCL:
			switch (tr_sym) {
			case EQUAL:
				return true;
			default:
				return false;
			}
		default:
			return true;
		}
	}

	/**
	 * Generate error token.
	 *
	 * @param lineNumber the line number
	 * @param Message the message
	 * @param StrBuffer the str buffer
	 */
	private void generateErrorToken(int lineNumber, String Message,
			String StrBuffer) {
		this.getReporter().reportError(lineNumber, "Lexer: " + Message + ": " + StrBuffer);
		this.setToken(TaggedToken.getInstance(lineNumber, TokenEnum.ERROR));
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see lexer.BaseLexer#GetInitialState()
	 */
	@Override
	protected StateEnum GetInitialState() {
		return StateEnum.INITIAL;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see lexer.BaseLexer#getNewState(java.lang.Object, java.lang.Object)
	 */
	protected StateEnum getNewState(TransitionValue tr_sym, StateEnum state) {
		switch (state) {

		case INITIAL:
			switch (tr_sym) {
			case LETTER:
				return StateEnum.IDENTIFIER;
			case DIGIT:
				return StateEnum.DIGIT;
			case DOUBLE_COM:
				return StateEnum.FIRST_D_COM;
			case LITERAL:
				return StateEnum.END;
			case SLASH:
				return StateEnum.SLASH;
			case PLUS:
				return StateEnum.END;
			case MINUS:
				return StateEnum.END;
			case EMPTY_VALUE:
				return StateEnum.INITIAL;
			case ASTERISK:
				return StateEnum.END;
			case GREATER:
				return StateEnum.GREATER;
			case LESS:
				return StateEnum.LESS;
			case EQUAL:
				return StateEnum.EQUAL;
			case EXCL:
				return StateEnum.EXCL;
			case EOF:
				return StateEnum.END;
			case DOT:
				return StateEnum.DECIMAL_PART;
			case D:
				return StateEnum.IDENTIFIER;
			case INVALID:
				return StateEnum.INITIAL;
			default:
				return StateEnum.END;
			}

		case IDENTIFIER:
			switch (tr_sym) {
			case LETTER:
				return StateEnum.IDENTIFIER;
			case D:
				return StateEnum.IDENTIFIER;
			case DIGIT:
				return StateEnum.IDENTIFIER;
			default:
				return StateEnum.END;
			}

		case FIRST_D_COM:
			switch (tr_sym) {
			case DOUBLE_COM:
				return StateEnum.END;
			case EOF:
				return StateEnum.INITIAL;
			default:
				return StateEnum.FIRST_D_COM;
			}

		case SLASH:
			switch (tr_sym) {
			case ASTERISK:
				return StateEnum.FIRST_ASTERISK;
			default:
				return StateEnum.END;
			}

		case FIRST_ASTERISK:
			switch (tr_sym) {
			case ASTERISK:
				return StateEnum.SECOND_ASTERISK;
			case EOF:
				return StateEnum.INITIAL;
			default:
				return StateEnum.FIRST_ASTERISK;
			}

		case SECOND_ASTERISK:
			switch (tr_sym) {
			case SLASH:
				return StateEnum.INITIAL;
			case EOF:
				return StateEnum.INITIAL;
			default:
				return StateEnum.FIRST_ASTERISK;
			}

		case DIGIT:
			switch (tr_sym) {
			case DIGIT:
				return StateEnum.DIGIT;
			case DOT:
				return StateEnum.DECIMAL_PART;
			case D:
				return StateEnum.EXPONENT;
			default:
				return StateEnum.END;
			}

		case DECIMAL_PART:
			switch (tr_sym) {
			case DIGIT:
				return StateEnum.DECIMAL_PART;
			case D:
				return StateEnum.EXPONENT;
			default:
				return StateEnum.END;
			}

		case EXPONENT:
			switch (tr_sym) {
			case DIGIT:
				return StateEnum.EXP_VALUE;
			case PLUS:
				return StateEnum.EXP_VALUE;
			case MINUS:
				return StateEnum.EXP_VALUE;
			default:
				return StateEnum.END;
			}

		case EXP_VALUE:
			switch (tr_sym) {
			case DIGIT:
				return StateEnum.EXP_VALUE;
			default:
				return StateEnum.END;
			}

		default:
			return StateEnum.END;
		}
	}

	/**
	 * Gets the sym table.
	 *
	 * @return the sym table
	 */
	public ISymTable getSymTable() {
		return symTable;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see lexer.BaseLexer#internalGetToken()
	 */
	@Override
	protected Token internalGetToken() {

		return internal_Token;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see lexer.BaseLexer#isEndState(java.lang.Object)
	 */
	@Override
	protected boolean isEndState(StateEnum state) {
		return state == StateEnum.END;
	}

	/**
	 * Parses the doble.
	 *
	 * @param lineNumber the line number
	 * @param DoubleToParse the double to parse
	 */
	private void parseDoble(int lineNumber, String DoubleToParse) {
		Double MyDouble = 0.0;
		try {
			MyDouble = Double.parseDouble(DoubleToParse + 'd');
			if (MyDouble.isInfinite() || MyDouble.isNaN())
				throw new NumberFormatException();
			addSymTable("_CTE_"+MyDouble.toString().replace(".", "_"), lineNumber, TokenEnum.CONSTANT,
					EntryType.DOUBLE, MyDouble);
		} catch (NumberFormatException e) {
			generateErrorToken(lineNumber, "Unrecognized Digit", DoubleToParse);
		}
	}

	/**
	 * Parses the int.
	 *
	 * @param lineNumber the line number
	 * @param IntToParse the int to parse
	 */
	private void parseInt(int lineNumber, String IntToParse) {
		Short MyInt = 0;
		try {
			MyInt = Short.parseShort(IntToParse);
			addSymTable(MyInt.toString(), lineNumber, TokenEnum.CONSTANT,
					EntryType.INT, MyInt);
		} catch (NumberFormatException e) {
			generateErrorToken(lineNumber, "Unrecognized Digit", IntToParse);
		}
	}

	/**
	 * Sets the sym table.
	 *
	 * @param symTable the new sym table
	 */
	protected void setSymTable(ISymTable symTable) {
		this.symTable = symTable;
	}

	/**
	 * Sets the token.
	 *
	 * @param itoken the new token
	 */
	protected void setToken(Token itoken) {
		internal_Token = itoken;
	}

	/*
	 * (non-Javadoc)
	 *
	 * @see lexer.BaseLexer#traduct(int)
	 */
	protected TransitionValue traduct(int input) {
		if ((input == 'd') || (input == 'D'))
			return TransitionValue.D;
		if ((input >= 'A') && (input <= 'z'))
			return TransitionValue.LETTER;
		if ((input >= '0') && (input <= '9'))
			return TransitionValue.DIGIT;
		switch (input) {
		case '\n':
			return TransitionValue.EMPTY_VALUE;
		case ' ':
			return TransitionValue.EMPTY_VALUE;
		case '\t':
			return TransitionValue.EMPTY_VALUE;
		case '\r':
			return TransitionValue.EMPTY_VALUE;
		case '\0':
			return TransitionValue.EMPTY_VALUE;
		case '\f':
			return TransitionValue.EMPTY_VALUE;
		case '*':
			return TransitionValue.ASTERISK;
		case '/':
			return TransitionValue.SLASH;
		case '+':
			return TransitionValue.PLUS;
		case '-':
			return TransitionValue.MINUS;
		case '>':
			return TransitionValue.GREATER;
		case '<':
			return TransitionValue.LESS;
		case '=':
			return TransitionValue.EQUAL;
		case '!':
			return TransitionValue.EXCL;
		case '{':
			return TransitionValue.LITERAL;
		case '}':
			return TransitionValue.LITERAL;
		case '(':
			return TransitionValue.LITERAL;
		case ')':
			return TransitionValue.LITERAL;
		case -1:
			return TransitionValue.EOF;
		case '.':
			return TransitionValue.DOT;
		case ';':
			return TransitionValue.LITERAL;
		case ',':
			return TransitionValue.LITERAL;
		case '\"':
			return TransitionValue.DOUBLE_COM;
		default:
			return TransitionValue.INVALID;
		}
	}
}
